package examples;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.layout.Layout;
import prefuse.action.layout.graph.SquarifiedTreeMapLayout;
import prefuse.controls.ControlAdapter;
import prefuse.data.Schema;
import prefuse.data.Tree;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.io.TreeMLReader;
import prefuse.data.query.SearchQueryBinding;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.ColorMap;
import prefuse.util.FontLib;
import prefuse.util.PrefuseLib;
import prefuse.util.UpdateListener;
import prefuse.util.ui.JFastLabel;
import prefuse.util.ui.JSearchPanel;
import prefuse.util.ui.UILib;
import prefuse.visual.DecoratorItem;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTree;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.TreeDepthItemSorter;

/**
 * Demonstration showcasing a TreeMap layout of a hierarchical data set and the
 * use of dynamic query binding for text search. Animation is used to highlight
 * changing search results.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class TreeMap extends Display {

  public static final String TREE_CHI = "chi-ontology.xml.gz";

  // create data description of labels, setting colors, fonts ahead of time
  private static final Schema LABEL_SCHEMA = PrefuseLib.getVisualItemSchema();
  static {
    LABEL_SCHEMA.setDefault(VisualItem.INTERACTIVE, false);
    LABEL_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(200));
    LABEL_SCHEMA.setDefault(VisualItem.FONT, FontLib.getFont("Tahoma", 16));
  }

  private static final String tree = "tree";
  private static final String treeNodes = "tree.nodes";
  private static final String treeEdges = "tree.edges";
  private static final String labels = "labels";

  private SearchQueryBinding searchQ;

  public TreeMap(Tree t, String label) {
    super(new Visualization());

    // add the tree to the visualization
    VisualTree vt = m_vis.addTree(tree, t);
    m_vis.setVisible(treeEdges, null, false);

    // ensure that only leaf nodes are interactive
    Predicate noLeaf = (Predicate) ExpressionParser.parse("childcount()>0");
    m_vis.setInteractive(treeNodes, noLeaf, false);

    // add labels to the visualization
    // first create a filter to show labels only at top-level nodes
    Predicate labelP = (Predicate) ExpressionParser.parse("treedepth()=1");
    // now create the labels as decorators of the nodes
    m_vis.addDecorators(labels, treeNodes, labelP, LABEL_SCHEMA);

    // set up the renderers - one for nodes and one for labels
    DefaultRendererFactory rf = new DefaultRendererFactory();
    rf.add(new InGroupPredicate(treeNodes), new NodeRenderer());
    rf.add(new InGroupPredicate(labels), new LabelRenderer(label));
    m_vis.setRendererFactory(rf);

    // border colors
    final ColorAction borderColor = new BorderColorAction(treeNodes);
    final ColorAction fillColor = new FillColorAction(treeNodes);

    // color settings
    ActionList colors = new ActionList();
    colors.add(fillColor);
    colors.add(borderColor);
    m_vis.putAction("colors", colors);

    // animate paint change
    ActionList animatePaint = new ActionList(400);
    animatePaint.add(new ColorAnimator(treeNodes));
    animatePaint.add(new RepaintAction());
    m_vis.putAction("animatePaint", animatePaint);

    // create the single filtering and layout action list
    ActionList layout = new ActionList();
    layout.add(new SquarifiedTreeMapLayout(tree));
    layout.add(new LabelLayout(labels));
    layout.add(colors);
    layout.add(new RepaintAction());
    m_vis.putAction("layout", layout);

    // initialize our display
    setSize(700, 600);
    setItemSorter(new TreeDepthItemSorter());
    addControlListener(new ControlAdapter() {
      public void itemEntered(VisualItem item, MouseEvent e) {
        item.setStrokeColor(borderColor.getColor(item));
        item.getVisualization().repaint();
      }

      public void itemExited(VisualItem item, MouseEvent e) {
        item.setStrokeColor(item.getEndStrokeColor());
        item.getVisualization().repaint();
      }
    });

    searchQ = new SearchQueryBinding(vt.getNodeTable(), label);
    m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, searchQ.getSearchSet());
    searchQ.getPredicate().addExpressionListener(new UpdateListener() {
      public void update(Object src) {
        m_vis.cancel("animatePaint");
        m_vis.run("colors");
        m_vis.run("animatePaint");
      }
    });

    // perform layout
    m_vis.run("layout");
  }

  public SearchQueryBinding getSearchQuery() {
    return searchQ;
  }

  public static void main(String argv[]) {
    UILib.setPlatformLookAndFeel();

    String infile = TREE_CHI;
    String label = "name";
    if (argv.length > 1) {
      infile = argv[0];
      label = argv[1];
    }
    JComponent treemap = demo(infile, label);

    JFrame frame = new JFrame("p r e f u s e | t r e e m a p");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setContentPane(treemap);
    frame.pack();
    frame.setVisible(true);
  }

  public static JComponent demo() {
    return demo(TREE_CHI, "name");
  }

  public static JComponent demo(String datafile, final String label) {
    Tree t = null;
    try {
      t = (Tree) new TreeMLReader().readGraph(datafile);
    } catch (Exception e) {
      e.printStackTrace();
      System.exit(1);
    }

    // create a new treemap
    final TreeMap treemap = new TreeMap(t, label);

    // create a search panel for the tree map
    JSearchPanel search = treemap.getSearchQuery().createSearchPanel();
    search.setShowResultCount(true);
    search.setBorder(BorderFactory.createEmptyBorder(5, 5, 4, 0));
    search.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 11));

    final JFastLabel title = new JFastLabel(" ");
    title.setPreferredSize(new Dimension(350, 20));
    title.setVerticalAlignment(SwingConstants.BOTTOM);
    title.setBorder(BorderFactory.createEmptyBorder(3, 0, 0, 0));
    title.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 16));

    treemap.addControlListener(new ControlAdapter() {
      public void itemEntered(VisualItem item, MouseEvent e) {
        title.setText(item.getString(label));
      }

      public void itemExited(VisualItem item, MouseEvent e) {
        title.setText(null);
      }
    });

    Box box = UILib.getBox(new Component[] { title, search }, true, 10, 3, 0);

    JPanel panel = new JPanel(new BorderLayout());
    panel.add(treemap, BorderLayout.CENTER);
    panel.add(box, BorderLayout.SOUTH);
    UILib.setColor(panel, Color.BLACK, Color.GRAY);
    return panel;
  }

  // ------------------------------------------------------------------------

  /**
   * Set the stroke color for drawing treemap node outlines. A graded grayscale
   * ramp is used, with higer nodes in the tree drawn in lighter shades of gray.
   */
  public static class BorderColorAction extends ColorAction {

    public BorderColorAction(String group) {
      super(group, VisualItem.STROKECOLOR);
    }

    public int getColor(VisualItem item) {
      NodeItem nitem = (NodeItem) item;
      if (nitem.isHover())
        return ColorLib.rgb(99, 130, 191);

      int depth = nitem.getDepth();
      if (depth < 2) {
        return ColorLib.gray(100);
      } else if (depth < 4) {
        return ColorLib.gray(75);
      } else {
        return ColorLib.gray(50);
      }
    }
  }

  /**
   * Set fill colors for treemap nodes. Search items are colored in pink, while
   * normal nodes are shaded according to their depth in the tree.
   */
  public static class FillColorAction extends ColorAction {
    private ColorMap cmap = new ColorMap(ColorLib.getInterpolatedPalette(10,
        ColorLib.rgb(85, 85, 85), ColorLib.rgb(0, 0, 0)), 0, 9);

    public FillColorAction(String group) {
      super(group, VisualItem.FILLCOLOR);
    }

    public int getColor(VisualItem item) {
      if (item instanceof NodeItem) {
        NodeItem nitem = (NodeItem) item;
        if (nitem.getChildCount() > 0) {
          return 0; // no fill for parent nodes
        } else {
          if (m_vis.isInGroup(item, Visualization.SEARCH_ITEMS))
            return ColorLib.rgb(191, 99, 130);
          else
            return cmap.getColor(nitem.getDepth());
        }
      } else {
        return cmap.getColor(0);
      }
    }

  } // end of inner class TreeMapColorAction

  /**
   * Set label positions. Labels are assumed to be DecoratorItem instances,
   * decorating their respective nodes. The layout simply gets the bounds of the
   * decorated node and assigns the label coordinates to the center of those
   * bounds.
   */
  public static class LabelLayout extends Layout {
    public LabelLayout(String group) {
      super(group);
    }

    public void run(double frac) {
      Iterator iter = m_vis.items(m_group);
      while (iter.hasNext()) {
        DecoratorItem item = (DecoratorItem) iter.next();
        VisualItem node = item.getDecoratedItem();
        Rectangle2D bounds = node.getBounds();
        setX(item, null, bounds.getCenterX());
        setY(item, null, bounds.getCenterY());
      }
    }
  } // end of inner class LabelLayout

  /**
   * A renderer for treemap nodes. Draws simple rectangles, but defers the
   * bounds management to the layout.
   */
  public static class NodeRenderer extends AbstractShapeRenderer {
    private Rectangle2D m_bounds = new Rectangle2D.Double();

    public NodeRenderer() {
      m_manageBounds = false;
    }

    protected Shape getRawShape(VisualItem item) {
      m_bounds.setRect(item.getBounds());
      return m_bounds;
    }
  } // end of inner class NodeRenderer

} // end of class TreeMap
